package network.ssl;

//import ihm.panel.camera.display.VLCPanel;

import ihm.panel.camera.display.VLCPanel;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Map.Entry;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import network.messages.KrakenClientMsg;
import network.messages.KrakenServerMsg;

import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

import bdd.sql.server.bean.Keystore;
import bdd.sql.server.bean.ServerCentral;
import bdd.sql.server.bean.ServerSite;
import bdd.sql.server.bean.User;
import bdd.sql.server.hibernate.HibernateUtil;

import security.aes.AES256;
import tools.AppletConstant;
import tools.ConfParser;
import tools.FileUtils;
import tools.StringUtils;
import ucp.projects.kraken.kamcenter.plugin.CameraFrame;

/**
 * Client TCP.
 * @author Guillaume Poças & Jean-François Augé
 */
public class KrakenSSLClient {

	//private static final int DEFAULT_PORT_NUMBER = 18000;
	//local
	//private static final String PATH_CONF_FILE = "C:\\Documents and Settings\\Guillaume\\workspace\\vsm_M2_reseau\\conf\\sslserver.conf";
	private static final String PATH_CONF_FILE = AppletConstant.SSL_CONF_FILE;
	//private String PATH_CONF_FILE = "";
	
	//serv web
	//private static final String PATH_CONF_FILE = "C:\\Serveur Java\\sslserver.conf";
	//serv ubuntu
	//private static final String PATH_CONF_FILE = "/etc/kraken/sslserver.conf";
	
	private static final String PATH_KEYSTORE = "PATH_KEYSTORE";
	private static final String KEYSTORE_PASSWORD = "KEYSTORE_PASSWORD";
	private static final String DEFAULT_PORT = "DEFAULT_PORT";
	private static final String VIDEO_PORT = "VIDEO_PORT";
	
	private SSLSocket sslsocket;
	//private Socket socket;
	private BufferedReader in;
	private PrintWriter out;
	private String servName;
	private String final_answer;
	private ArrayList<String> message_list;
	private boolean finished;
	private byte[] saved_data;
	
	private int auth = 0;
	
	// data
	private BigInteger B;
	
	private CameraFrame frame;
	private long frame_count = 0;
	private int fps = 0;
	private int last_fps_sent = 0;
	// 0 default, 1 video
	private int type = 0;
	private boolean stream_end = false;
	
	// 0 stop, 1 play, 2 pause
	private int stream_state = 0;
	
	private VLCPanel VCL_jp;
	
	/**
	 * Constructeur.
	 * @param msg
	 * @param nameServer
	 */
	public KrakenSSLClient(ArrayList<String> msg, String nameServer, int type) {
		this.sslsocket = null;
		this.in = null;
		this.out = null;
		this.servName = nameServer;
		this.final_answer = "";
		this.message_list = msg;
		this.finished = false;
		this.type = type;
		this.saved_data = new byte[0];
		
		//PATH_CONF_FILE = AppletConstant.SSL_CONF_FILE;
	}
	
	/**
	 * Se connecter au serveur TCP.
	 */
	public void connect() {
		try {
			
			ConfParser parser = new ConfParser();
			try {
				parser.parse(new File(PATH_CONF_FILE)); //debug ?
				//parser.parse(new File(AppletConstant.SSL_CONF_FILE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			String keystorePath = parser.getValue(PATH_KEYSTORE);
			String keystorePassword = parser.getValue(KEYSTORE_PASSWORD);
			
			int default_port_number = 0;
			if(type == 1)
				default_port_number = new Integer(parser.getValue(VIDEO_PORT));
			else
				default_port_number = new Integer(parser.getValue(DEFAULT_PORT));
			System.out.println("\tUsing port "+default_port_number);
			
			//init à partir d'un fichier de conf !
			//default_port_number=18001;
			initSSL(keystorePath, keystorePassword);
			SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
			sslsocket = (SSLSocket) sslsocketfactory.createSocket(servName, default_port_number);
			//socket = new Socket(servName, DEFAULT_PORT_NUMBER);
			
			//if(type == 0)
			in = new BufferedReader(new InputStreamReader(sslsocket.getInputStream()));
			out = new PrintWriter(sslsocket.getOutputStream(),true);
			//System.out.println("Connecté à "+servName+":"+DEFAULT_PORT_NUMBER);
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
	/**
	 * Se connecter au serveur TCP.
	 */
	public void connectWithPassword(String password) {
		try {
			
			ConfParser parser = new ConfParser();
			try {
				//parser.parse(new File(PATH_CONF_FILE)); //debug ?
				parser.parse(new File(AppletConstant.SSL_CONF_FILE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			String keystorePath = parser.getValue(PATH_KEYSTORE);
			String keystorePassword = password;
			int default_port_number = new Integer(parser.getValue(DEFAULT_PORT));
			
			//init à partir d'un fichier de conf !
			initSSL(keystorePath, keystorePassword);
			SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
			sslsocket = (SSLSocket) sslsocketfactory.createSocket(servName, default_port_number);
			//socket = new Socket(servName, DEFAULT_PORT_NUMBER);
			
			in = new BufferedReader(new InputStreamReader(sslsocket.getInputStream()));
			out = new PrintWriter(sslsocket.getOutputStream(),true);   
			//System.out.println("Connecté à "+servName+":"+DEFAULT_PORT_NUMBER);
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
	public void initSSL(String keyStore, String passKey) {
		System.setProperty("javax.net.ssl.trustStore", keyStore);
		System.setProperty("javax.net.ssl.trustStorePassword", passKey);
		System.setProperty("javax.net.ssl.keyStore", keyStore);
		System.setProperty("javax.net.ssl.keyStorePassword", passKey);
	}
	
	/**
	 * Envoyer un message au serveur TCP.
	 * Attente bloquante d'une réponse.
	 * @param msg
	 * @return
	 */
	public String sendMessage(String msg) {
		String answer = "";
		//envoie le message au serveur
		out.println(msg);
		out.flush();
		try{
			//réceptionne la réponse
			answer = in.readLine();
			//System.out.println("Reponse du serveur"+answer);
	    } catch (IOException e2){
	    	System.out.println("Read failed "+e2);
	    	System.exit(1);
	    }
		
        return answer;
	}
	
	/**
	 * Envoyer un message au serveur TCP.
	 * @param msg
	 */
	public void sendMessage_noAnwser(String msg) {
		//envoie le message au serveur
		out.println(msg);
		out.flush();
	}
	
	/**
	 * Démarre la communication avec le serveur.
	 */
	public void communicate() {
		int i = 0;
		String state = "";
		String msgCmd[];
		while(!finished){
			System.out.println("i = "+i+" et size "+message_list.size());
			System.out.println(message_list.get(i));
			state = sendMessage(message_list.get(i));
			if(frame_count > 0)
				System.out.println("vid rep serv : "+state);
			else
				System.out.println("cmd rep serv : "+state);
			msgCmd = state.split("  ");
			
			if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.SEND_CONFIGURATION.toString())) {
				if(msgCmd.length == 3) {
					FileUtils.tcpWaitingFileByWeight(this.sslsocket, msgCmd[2], Long.valueOf(msgCmd[1]));
				}
			}
			//traitements intermédiaires
			//réceptions de vidéos
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.START_DOWNLOAD.toString())){
					if(msgCmd.length == 5){
					//1 = type & 2 = nombre fichier & 3 = noms fichiers & 4 = poids fichiers					
					int maxFichier = Integer.parseInt(msgCmd[2]);
					String[] nomsFichiers = msgCmd[3].split(":");
					String[] poidsFichiers = msgCmd[4].split(":");
					
					ConfParser parser = new ConfParser();
					try {
						parser.parse(new File(AppletConstant.CLIENT_CONF_FILE));
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					String path = parser.getValue("DOWNLOADED_VIDEO_PATH");
					System.out.println(KrakenServerMsg.START_DOWNLOAD+" "+maxFichier);
					
					ConfParser parserHist = new ConfParser();
					File file = new File(AppletConstant.HIST_CONF_FILE);
					
					try {
						parserHist.parse(file);
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					
					for(int cpt = 0; cpt < maxFichier; cpt++) {
						System.out.println("Attente fichier "+(cpt+1)+" de poids "+poidsFichiers[cpt]);
						FileUtils.tcpWaitingFileByWeight(this.sslsocket,path+nomsFichiers[cpt],Long.valueOf(poidsFichiers[cpt]));
						
						if(msgCmd[1].equals("video")){
							parserHist.addField(nomsFichiers[cpt], path+nomsFichiers[cpt]);
						}
						else if(msgCmd[1].equals("keystore")){
							String keystorePath = path+nomsFichiers[cpt];
							
							Session session = HibernateUtil.getSession();
							String user_name = ((User)session.createCriteria(User.class).
									add(Restrictions.like("user_id", this.VCL_jp.getApplet().getUser_ID())).list().get(0)).getUser_name();
							Keystore keystore = (Keystore) session.createCriteria(Keystore.class).
									add(Restrictions.like("key_name", nomsFichiers[cpt])).
									add(Restrictions.like("key_user", user_name)).list().get(0);
							String videoName = keystore.getVideo().getVideo_name().replaceAll(".mp4", "_"+user_name+".mp4");
							String encryptedVideoPath = parserHist.getValue(videoName);
							String clearVideoPath = encryptedVideoPath.replaceAll(".encrypt", "");
							AES256.decryptInputFile(encryptedVideoPath,
									clearVideoPath,
									keystorePath,
									keystore.getKey_password(),
									keystore.getKey_containedKeyPassword(),
									keystore.getKey_alias(),
									keystore.getKey_iv());
							session.close();
							new File(keystorePath).delete();
							
							VCL_jp.addVideo(clearVideoPath);

							if(cpt == 0){
								VCL_jp.playFirst();
							}
						}
					}
					try {
						parserHist.saveInFile(file,	parserHist.getChamps());
						parserHist.saveInFile(new File(AppletConstant.HIST_CONF_FILE), parserHist.getChamps());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
			
			//envoie de fichier
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.WAITING_FILE.toString())){
				if(msgCmd.length == 3){
					FileUtils.tcpSendFile(sslsocket, msgCmd[1], msgCmd[2]);
					
					//reçoit la fin d'attente du serveur
					try {
						state = in.readLine();
						msgCmd = state.split("  ");
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					//envoie la fin de l'envoie du fichier
					if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.END_WAITING_FILE.toString())){
						state = sendMessage(KrakenClientMsg.FILE_SENDED.makeCommand());
						msgCmd = state.split("  ");
					}
				}
			}
			
			//distribution des classes de chiffrement, serv web vers global
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.PATH_ENCRYPTION_DISTRIBUTION.toString())){
				if(msgCmd.length == 4){
					if(msgCmd[1].equalsIgnoreCase("GLOBAL")){
						//1 = GLOBAL & 2 = chemin serv web des fichiers & 3 = chemin serv global où copier les fichiers
						String save_msg = message_list.get(message_list.size()-1);
						int j = i+1;
						File[] files = FileUtils.listFiles(msgCmd[2]);
						
						message_list.remove(j);
						message_list.add(KrakenClientMsg.CLEAN_FOLDER.makeCommand(" "+msgCmd[3]));
						for(File file : files){
							j++;
							//if(j == message_list.size())	
							message_list.add(KrakenClientMsg.COPY_FILE.makeCommand(" "+file.getAbsolutePath(), " "+msgCmd[3]+file.getName()));
						}
	
						Session session = HibernateUtil.getSession();
						ServerCentral server = (ServerCentral) session.createCriteria(ServerCentral.class).
								add(Restrictions.like("globserv_ip", servName)).list().get(0);
						for(ServerSite site : server.getSiteServers())
							message_list.add(KrakenClientMsg.ENCRYPTION_DISTRIBUTION.makeCommand(" LOCAL", " "+site.getSiteserv_ip()," "+msgCmd[3]));
							//=> reaction à ce message = 1ère partie de celui là sans celle là
						message_list.add(save_msg);
					}
					else if(msgCmd[1].equalsIgnoreCase("LOCAL")){
						//1 = LOCAL & 2 = chemin serv global des fichiers & 3 = chemin serv local où copier les fichiers
						String save_msg = message_list.get(message_list.size()-1);
						int j = i+1;
						File[] files = FileUtils.listFiles(msgCmd[2]);
						
						message_list.remove(j);
						message_list.add(KrakenClientMsg.CLEAN_FOLDER.makeCommand(" "+msgCmd[3]));
						for(File file : files){
							j++;
							//if(j == message_list.size())	
							message_list.add(KrakenClientMsg.COPY_FILE.makeCommand(" "+file.getAbsolutePath(), " "+msgCmd[3]+file.getName()));
						}
						message_list.add(save_msg);
					}
				}
			}
			
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.PATH_ENCRYPTION_PARAMETER.toString())){
				if(msgCmd.length == 4){
					//1 = nom param & 2 = répertoire des classes serv local & 3 = rep du parameter
					String save_msg = message_list.get(message_list.size()-1);
					int j = i+1;
					File[] files = FileUtils.listFiles(msgCmd[2]);
					
					message_list.remove(j);
					message_list.add(KrakenClientMsg.CLEAN_FOLDER.makeCommand(" "+msgCmd[3]));
					for(File file : files){
						j++;
						if(file.getName().equalsIgnoreCase(msgCmd[1]))
							message_list.add(KrakenClientMsg.COPY_FILE.makeCommand(" "+file.getAbsolutePath(), " "+msgCmd[3]));
					}
					message_list.add(save_msg);
				}
			}
			
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.DH_ACK.toString())) {
				System.out.println("ok");
			}
			
			// protocole diffie-hellman
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.SEND_B.toString())) {
				// stocker B
				if(msgCmd.length == 2)
					B = new BigInteger(msgCmd[1]);
			}
			
			// récupération du jeton d'authentification
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.SEND_GENERATED_VIDEO_TOKEN.toString())){
				if(msgCmd.length == 2){
					saved_data = StringUtils.stringToBytes(msgCmd[1]);
					auth = 1;
				}
			}
			
			// debut du streaming
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.START_STREAM.toString())) {
				if(msgCmd.length == 2) {
					if(msgCmd[1].equals("cmd")) {
						// gerer les commandes
						while(stream_state != 0) {
							if(stream_state == 2) {
								System.out.println("pause stream");
								sendMessage_noAnwser(KrakenClientMsg.PAUSE_STREAM.makeCommand());
								while(stream_state == 2) {
									try {
										Thread.sleep(100);
									} catch (InterruptedException e) {
										e.printStackTrace();
									}
								}
							} else if(fps != last_fps_sent && fps > 1) {
								last_fps_sent = fps;

								System.out.println("report fps "+fps);
								state = sendMessage(KrakenClientMsg.REPORT_FPS.makeCommand(" "+fps));
								System.out.println("rep serv : "+state); //Debug
							}
						}
						
						// end stream
						System.out.println("stop stream");
						sendMessage_noAnwser(KrakenClientMsg.STOP_STREAM.makeCommand());
					} else if(msgCmd[1].equals("vid")) {
						// gerer la video
						ObjectInputStream inputStream;
						try {
							inputStream = new ObjectInputStream(sslsocket.getInputStream());
							while(stream_state != 0 && stream_end != true) {
								while(stream_state == 2) {
									try {
										// pause
										Thread.sleep(100);
									} catch (InterruptedException e) {
										e.printStackTrace();
									}
								}
								
								try {
									frame = (CameraFrame) inputStream.readObject();
								} catch(EOFException e) {
									System.err.println("stop failed");
								}
								frame_count++;
								System.out.println(frame_count);
							}
							
							System.out.println("stop video stream");
							//state = sendMessage(KrakenClientMsg.END_SSL_CLIENT.makeCommand());
							//state = in.readLine();
							//System.out.println("rep serv : "+state); //Debug
							finished = true;
							
							// end stream
							/*try {
								state = in.readLine();
								msgCmd = state.split("  ");
								if(msgCmd.equals(KrakenServerMsg.END_STREAM))
									stream_end = true;
								
								finished = true;
							} catch (IOException e) {
								e.printStackTrace();
							}*/
							frame = null;
						} catch (IOException e) {
							e.printStackTrace();
						} catch (ClassNotFoundException e) {
							e.printStackTrace();
						}
					}
				}
			}
			
			// téléchargement des vidéos
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.VIDEO_SERVICE_AUTHENTIFICATED.toString())){
				// TODO dl
				// dechiffrer
				// ajouter dans la liste des vidéos à lire
				System.out.println(KrakenServerMsg.VIDEO_SERVICE_AUTHENTIFICATED);
				
				/*String folder = System.getProperty("user.home")+"\\kraken\\";

				VCL_jp.getVideo_list().add(folder+"0.avi");
				VCL_jp.getVideo_list().add(folder+"1.avi");
				VCL_jp.getVideo_list().add(folder+"2.avi");
				VCL_jp.playFirst();*/
			}

			/*else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.START_DOWNLOAD.toString())) {
				System.out.println(KrakenServerMsg.START_DOWNLOAD);
				
			}*/
			
			//bon déroulement de fin
			if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.END_SSL_SERVER.toString())){
				this.final_answer = state;
				finished = true;
			}
			//problème
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.TIMEOUT.toString())){
				this.final_answer = KrakenServerMsg.TIMEOUT.makeCommand(" "+message_list.get(i));
				finished = true;
			}
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.ERROR.toString())){
				this.final_answer = KrakenServerMsg.ERROR.makeCommand(" "+message_list.get(i));
				finished = true;
			}
			else if(msgCmd[0].equalsIgnoreCase(KrakenServerMsg.UNKNOWN_COMMAND.toString())){
				this.final_answer = KrakenServerMsg.UNKNOWN_COMMAND.makeCommand(" "+message_list.get(i));
				finished = true;
			}
			//pas de fin "normale" = pas de CLIENT_END envoyé au serveur...
			else if(i == message_list.size()){
				this.final_answer = KrakenServerMsg.ERROR.makeCommand(" NO_END_SENT");
				finished = true;
			}
			i++;
		}
	}

	public ArrayList<String> getMessages() { return this.message_list; }
	public void setMessages(ArrayList<String> msg) { this.message_list = msg; }
	public String getFinalAnswer(){ return this.final_answer;	}
	
	public byte[] getSaved_data() {	return saved_data;	}
	public void setSaved_data(byte[] saved_data) {	this.saved_data = saved_data;	}

	public BigInteger getB() { return B; }
	public void setB(BigInteger b) { B = b; }

	public int getFps() { return fps; }
	public void setFps(int fps) { this.fps = fps; }

	public CameraFrame getFrame() { return frame; }
	public void setFrame(CameraFrame frame) { this.frame = frame; }

	public int getAuth() { return auth; }
	public void setAuth(int auth) { this.auth = auth; }

	public int getStream_state() { return stream_state; }
	public void setStream_state(int stream_state) { this.stream_state = stream_state; }

	public void setVCL_jp(VLCPanel vlc_jp) { this.VCL_jp = vlc_jp; }

}
